/**
 * VERSION: 2.0
 * DATE: 2010-08-07
 * ACTIONSCRIPT VERSION: 3.0 
 * UPDATES AND DOCUMENTATION AT: http://www.TweenMax.com
 **/
package com.greensock.plugins {
	import com.greensock.*;
	import com.greensock.core.*;
	
	import flash.display.*;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.getDefinitionByName;
/**
 * Normally, all transformations (scale, rotation, and position) are based on the DisplayObject's registration
 * point (most often its upper left corner), but TransformAroundPoint allows you to define ANY point around which
 * transformations will occur during the tween. For example, you may have a dynamically-loaded image that you 
 * want to scale from its center or rotate around a particular point on the stage. <br /><br />
 * 
 * If you define an x or y value in the transformAroundPoint object, it will correspond to the custom registration
 * point which makes it easy to position (as opposed to having to figure out where the original registration point 
 * should tween to). If you prefer to define the x/y in relation to the original registration point, do so outside 
 * the transformAroundPoint object, like: <br /><br /><code>
 * 
 * TweenLite.to(mc, 3, {x:50, y:40, transformAroundPoint:{point:new Point(200, 300), scale:0.5, rotation:30}});<br /><br /></code>
 * 
 * TransformAroundPointPlugin is a <a href="http://www.greensock.com/club/">Club GreenSock</a> membership benefit. 
 * You must have a valid membership to use this class without violating the terms of use. Visit 
 * <a href="http://www.greensock.com/club/">http://www.greensock.com/club/</a> to sign up or get more details. <br /><br />
 * 
 * <b>USAGE:</b><br /><br />
 * <code>
 * 		import com.greensock.TweenLite; <br />
 * 		import com.greensock.plugins.TweenPlugin; <br />
 * 		import com.greensock.plugins.TransformAroundPointPlugin; <br />
 * 		TweenPlugin.activate([TransformAroundPointPlugin]); //activation is permanent in the SWF, so this line only needs to be run once.<br /><br />
 * 
 * 		TweenLite.to(mc, 1, {transformAroundPoint:{point:new Point(100, 300), scaleX:2, scaleY:1.5, rotation:150}}); <br /><br />
 * </code>
 * 
 * <b>Copyright 2010, GreenSock. All rights reserved.</b> This work is subject to the terms in <a href="http://www.greensock.com/terms_of_use.html">http://www.greensock.com/terms_of_use.html</a> or for corporate Club GreenSock members, the software agreement that was issued with the corporate membership.
 * 
 * @author Jack Doyle, jack@greensock.com
 */
	public class TransformAroundPointPlugin extends TweenPlugin {
		/** @private **/
		public static const API:Number = 1.0; //If the API/Framework for plugins changes in the future, this number helps determine compatibility
		/** @private **/
		private static var _classInitted:Boolean;
		/** @private **/
		private static var _isFlex:Boolean;
		
		/** @private **/
		protected var _target:DisplayObject;
		/** @private **/
		protected var _local:Point;
		/** @private **/
		protected var _point:Point;
		/** @private **/
		protected var _shortRotation:ShortRotationPlugin;
		
		/** @private **/
		protected var _proxy:DisplayObject;
		/** @private **/
		protected var _proxySizeData:Object;
		
		/** @private **/
		public function TransformAroundPointPlugin() {
			super();
			this.propName = "transformAroundPoint";
			this.overwriteProps = ["x","y"];
			this.priority = -1; //so that the x/y tweens occur BEFORE the transformAroundPoint is applied
		}
		
		/** @private **/
		override public function onInitTween(target:Object, value:*, tween:TweenLite):Boolean {
			if (!(value.point is Point)) {
				return false;
			}
			_target = target as DisplayObject;
			_point = value.point.clone();
			_local = _target.globalToLocal(_target.parent.localToGlobal(_point));
			
			if (!_classInitted) {
				try {
					_isFlex = Boolean(getDefinitionByName("mx.managers.SystemManager")); // SystemManager is the first display class created within a Flex application
				} catch (e:Error) {
					_isFlex = false;
				}
				_classInitted = true;
			}
			
			if ((!isNaN(value.width) || !isNaN(value.height)) && _target.parent != null) {
				var m:Matrix = _target.transform.matrix;
				var point:Point = _target.parent.globalToLocal(_target.localToGlobal(new Point(100, 100)));
				_target.width *= 2;
				if (point.x == _target.parent.globalToLocal(_target.localToGlobal(new Point(100, 100))).x) {
					_proxy = _target;
					_target.rotation = 0;
					_proxySizeData = {};
					if (!isNaN(value.width)) {
						addTween(_proxySizeData, "width", _target.width / 2, value.width, "width"); //Components that alter their width without scaling will treat their width/height setters as though they were applied without any rotation, so we must handle these separately. If we just allow the width/height tweens to affect the Sprite and copy those values over to the _proxy, it won't behave properly.
					}
					if (!isNaN(value.height)) {
						addTween(_proxySizeData, "height", _target.height, value.height, "height");
					}
					var b:Rectangle = _target.getBounds(_target);
					var s:Sprite = new Sprite();
					var container:Sprite = _isFlex ? new (getDefinitionByName("mx.core.UIComponent"))() : new Sprite(); //in Flex, any thing we addChild() must be a UIComponent so we wrap our Sprite in one.
					container.addChild(s);
					container.visible = false;
					_proxy.parent.addChild(container);
					_target = s;
					s.graphics.beginFill(0x0000FF, 0.4);
					s.graphics.drawRect(b.x, b.y, b.width, b.height);
					s.graphics.endFill();
					s.transform.matrix = _target.transform.matrix = m;
				} else {
					_target.transform.matrix = m;
				}
			}
			
			var p:String, short:ShortRotationPlugin, sp:String;
			for (p in value) {
				if (p == "point") {
					//ignore - we already set it above
				} else if (p == "shortRotation") {
					_shortRotation = new ShortRotationPlugin();
					_shortRotation.onInitTween(_target, value[p], tween);
					addTween(_shortRotation, "changeFactor", 0, 1, "shortRotation");
					for (sp in value[p]) {
						this.overwriteProps[this.overwriteProps.length] = sp;
					}
				} else if (p == "x" || p == "y") {
					addTween(_point, p, _point[p], value[p], p);
					this.overwriteProps[this.overwriteProps.length] = p;
				} else if (p == "scale") {
					addTween(_target, "scaleX", _target.scaleX, value.scale, "scaleX");
					addTween(_target, "scaleY", _target.scaleY, value.scale, "scaleY");
					this.overwriteProps[this.overwriteProps.length] = "scaleX";
					this.overwriteProps[this.overwriteProps.length] = "scaleY";
				} else if ((p == "width" || p == "height") && _proxy != null) {
					//let the proxy handle width/height
				} else {
					addTween(_target, p, _target[p], value[p], p);
					this.overwriteProps[this.overwriteProps.length] = p;
				}
			}
			
			if (tween != null) {
				var enumerables:Object = tween.vars; 
				if ("x" in enumerables || "y" in enumerables) { //if the tween is supposed to affect x and y based on the original registration point, we need to make special adjustments here...
					var endX:Number, endY:Number;
					if ("x" in enumerables) {
						endX = (typeof(enumerables.x) == "number") ? enumerables.x : _target.x + Number(enumerables.x);
					}
					if ("y" in enumerables) {
						endY = (typeof(enumerables.y) == "number") ? enumerables.y : _target.y + Number(enumerables.y);
					}
					tween.killVars({x:true, y:true}, false); //we're taking over.
					this.changeFactor = 1;
					if (!isNaN(endX)) {
						addTween(_point, "x", _point.x, _point.x + (endX - _target.x), "x");
					}
					if (!isNaN(endY)) {
						addTween(_point, "y", _point.y, _point.y + (endY - _target.y), "y");
					}
					this.changeFactor = 0;
				}
			}
			
			return true;
		}
		
		/** @private **/
		override public function killProps(lookup:Object):void {
			if (_shortRotation != null) {
				_shortRotation.killProps(lookup);
				if (_shortRotation.overwriteProps.length == 0) {
					lookup.shortRotation = true;
				}
			}
			super.killProps(lookup);
		}
		
		/** @private **/
		override public function set changeFactor(n:Number):void {
			if (_proxy != null && _proxy.parent != null) {
				_proxy.parent.addChild(_target.parent);
			}
			var val:Number, x:Number, y:Number;
			if (_target.parent) {
				var p:Point, pt:PropTween, i:int = _tweens.length;
				if (this.round) {
					while (--i > -1) {
						pt = _tweens[i];
						val = pt.start + (pt.change * n);
						pt.target[pt.property] = (val > 0) ? int(val + 0.5) : int(val - 0.5); //4 times as fast as Math.round()
					}
					p = _target.parent.globalToLocal(_target.localToGlobal(_local));
					x = _target.x + _point.x - p.x;
					y = _target.y + _point.y - p.y;
					_target.x = (x > 0) ? int(x + 0.5) : int(x - 0.5); //4 times as fast as Math.round()
					_target.y = (y > 0) ? int(y + 0.5) : int(y - 0.5); //4 times as fast as Math.round()
				} else {
					while (--i > -1) {
						pt = _tweens[i];
						pt.target[pt.property] = pt.start + (pt.change * n);
					}
					p = _target.parent.globalToLocal(_target.localToGlobal(_local));
					_target.x += _point.x - p.x;
					_target.y += _point.y - p.y;
				}
			}
			_changeFactor = n;
			if (_proxy != null && _proxy.parent != null) {
				var r:Number = _target.rotation;
				_proxy.rotation = _target.rotation = 0;
				if (_proxySizeData.width != undefined) {
					_proxy.width = _target.width = _proxySizeData.width;
				}
				if (_proxySizeData.height != undefined) {
					_proxy.height = _target.height = _proxySizeData.height;
				}
				_proxy.rotation = _target.rotation = r;
				
				p = _target.parent.globalToLocal(_target.localToGlobal(_local));
				x = _target.x + _point.x - p.x;
				y = _target.y + _point.y - p.y;
				if (this.round) {
					_proxy.x = (x > 0) ? int(x + 0.5) : int(x - 0.5); //4 times as fast as Math.round()
					_proxy.y = (y > 0) ? int(y + 0.5) : int(y - 0.5); //4 times as fast as Math.round()
				} else {
					_proxy.x = x;
					_proxy.y = y;
				}
				_proxy.parent.removeChild(_target.parent);
			}
		}

	}
}